/***
*	The objects and methods contained here, define and implement a cross platform interface for networking using the TCP protocol
*	As well as dealing with the most common error cases.
*/

#include <winsock.h>
#include <string>
#include <sstream>
#include <map>
#include "platinum.h"

namespace Platinum{
	namespace Net{
		namespace TCP{
			
			/**
			*	TCP::Connection objects contain a socket and address objects
			*	They are used for tracking TCP Sockets and associating them with the remote addresses
			*/

			Connection::Connection()
			{ 
				Socket = INVALID_SOCKET;
			}

			Connection::~Connection()
			{	
				if(Socket != -1)
					{
						closesocket(Socket);
					}
			}

			typedef std::map<SOCKET, TCP::Connection* > ConnectionMap;

			/**
			*	The TCP::Initialize function initializes a Connection object 
			*	Initialization includes misc work such as setting address family and setting the socket to non-blocking
			*/
			bool Initialize(Connection* MyConn)
			{
				
				MyConn->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
				
				if (MyConn->Socket == INVALID_SOCKET) {
					std::stringstream msg;
					msg << "trying to initialize socket and recieved error."
						<< std::endl;
					throw Error(WSAGetLastError(),msg.str());
					//WSACleanup();
					return(false);
				}
				
				// set SO_REUSEADDR on a socket to true (1):
				BOOL bOptVal = TRUE;
				int bOptLen = sizeof(BOOL);
				int iOptLen = sizeof(int);
				setsockopt(MyConn->Socket, SOL_SOCKET, SO_REUSEADDR, (char*)&bOptVal, bOptLen);

				return(true);
			}

			/**
			*	TCP::Bind binds the connection to a given port
			*/
			bool Bind(Connection* MyConn, int iPort)
			{
				  //sockaddr_in service;
				  MyConn->Addr.sin_family = AF_INET;
				  MyConn->Addr.sin_addr.s_addr = INADDR_ANY;
				  MyConn->Addr.sin_port = htons(iPort);
				  memset(MyConn->Addr.sin_zero, '\0', sizeof MyConn->Addr.sin_zero);
				 
				int err = bind(MyConn->Socket, (SOCKADDR*)&MyConn->Addr, sizeof MyConn->Addr);
				if(err == SOCKET_ERROR)
				{
					//closesocket(MyConn->Socket);
					std::stringstream msg;
					msg << "Could not bind socket "
					 << MyConn->Socket
					 << " to port "
					 << iPort				
					 << " error code is "
					 << WSAGetLastError();
					
					throw Error(WSAGetLastError(),msg.str());
					return(false);
				}else{
					return(true);
				}
			}

			void SetBlocking(Connection* MyConn, bool state)
			{
					u_long iMode = 0;
					if(!state)
					{					
						iMode = 1; //Set iMode to nonblocking using any nonzero value
					}else{
						iMode = 0;
					}
					ioctlsocket(MyConn->Socket, FIONBIO, &iMode);
			}
			/**
			*	TCP::Connect establishes an out going connection to a given IP on a given port
			*/
			bool Connect(Connection* MyConn,std::string IP,int iPort)
			{
				MyConn->Addr.sin_family = AF_INET;          // host byte order
				MyConn->Addr.sin_port = htons(iPort);   // short, network byte order
				MyConn->Addr.sin_addr.s_addr = inet_addr(IP.c_str());
				memset(MyConn->Addr.sin_zero, '\0', sizeof MyConn->Addr.sin_zero);

				if(connect(MyConn->Socket, (struct sockaddr *)&MyConn->Addr, sizeof MyConn->Addr) == SOCKET_ERROR)
				{
					std::stringstream msg;
					msg << "Could not connect to remote host "
					<< inet_ntoa(MyConn->Addr.sin_addr)
					<< " on port "
					<< ntohs(MyConn->Addr.sin_port)
					<< " error code is "
					<< WSAGetLastError()
					<< std::endl;

					throw Error(WSAGetLastError(),msg.str());
					return(false);
				}else{
					return(true);
				}
			}


			/**
			*	TCP::Listen listens to a given Connection waiting for an incoming connection
			*	It will not block and returns a new Connection object if there is a waiting connection 
			*	or it will return the original Connection object if there was no waiting connection
			*/
			Connection* Listen(Connection* MyConn)
			{
				//std::cout << "TCP::Listen entered" << std::endl;
				Connection* Remote = new Connection;
				
				int sin_size = sizeof Remote->Addr;

				if(listen(MyConn->Socket, BACKLOG) != SOCKET_ERROR)
				{
					Remote->Socket = accept(MyConn->Socket, (struct sockaddr *)&Remote->Addr, &sin_size);
					if(Remote->Socket != INVALID_SOCKET)
					{						
						return(Remote);
					}else{
						if(WSAGetLastError() != WSAEWOULDBLOCK)
							throw(Error(WSAGetLastError()," calling accept() caused invalid socket!"));

						delete(Remote);
						return(MyConn);
					}
				}else{
					std::stringstream msg;
					msg << "Listen threw an error of "
						<< WSAGetLastError()
						<< std::endl;
					throw Error(WSAGetLastError(),msg.str());
				}
			}					

			/**
			*	TCP::Send will send data to a given Connection
			*	It will thow an exception if you pass it an empty string or if the data could not be sent,
			*	or if the connection was closesocketd
			*/
			bool Send(Connection* MyConn, std::string Data)
			{
				//std::cout << "TCP::Send entered for Socket " << MyConn->Socket << std::endl;
				int len, sent;
				len = Data.size();
				if(len > 0)
				{
					sent = send(MyConn->Socket, Data.c_str(),len-1,0);
					if(sent < len -1 && sent != -1)
					{
						std::stringstream msg;
						msg << "Only sent "
						<< sent
						<< " of "
						<< len
						<< " bytes!";
						throw(Error(WSAGetLastError(),msg.str()));
						return(false);
					}else{
						return(true);
					}
				}else{
					throw(Error(5,"You are attempting to send an empty string"));
					return(true);
				}
				
			}

			/**
			*	TCP::Recv will recieve data from a connection and return a string containing that data.
			*	It will throw an exception if the connection was closed.
			*/
			std::string Recv(Connection* MyConn)
			{
				//std::cout << "TCP::Recv entered for Socket " << MyConn->Socket << std::endl;
				std::string msg;
				char buf[MAXBUF+1];
				memset(buf,'\0',MAXBUF);
				int bytes = recv(MyConn->Socket,buf,MAXBUF,0);
				if(bytes == 0)
				{
					throw(Error(WSAGetLastError(),"Remote Connection was closed"));
				}else{
					if(bytes != -1)
					{
						msg = buf;
						return(msg);
					}else{
						return(msg);
					}
				}
			}
		}
	}
}

